package org.tools.xml;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.net.URL;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.xml.XMLConstants;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import javax.xml.validation.Validator;

import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.dom4j.io.DocumentResult;
import org.dom4j.io.DocumentSource;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.SAXReader;
import org.dom4j.io.XMLWriter;
import org.tools.common.BeanUtil;
import org.tools.constants.StringPool;
import org.tools.file.FileUtil;
import org.tools.string.StringUtil;
import org.xml.sax.SAXException;

/**
 * xml操作类。<br>
 * 包括xml的读取，xml的转换等。
 * 
 * <pre>
 *  
 * 构建组：e-tools
 * 作者：eddy
 * 邮箱：xqxyxchy@126.com
 * 日期：2017年4月14日-下午9:53:00
 * </pre>
 */
public class Dom4jUtil {

	/**
	 * 将符合格式的xml字符串 转化成 Document
	 * 
	 * @param s
	 * @return
	 */
	public static Document loadXml(String s) {
		Document document = null;
		try {
			document = DocumentHelper.parseText(s);
		} catch (Exception ex) {
			ex.printStackTrace();
		}
		return document;
	}

	/**
	 * 加载一个XML文件转成Document对象
	 * 
	 * @param filename
	 * @return
	 */
	public static Document load(String filename, String encode) {
		Document document = null;
		try {
			SAXReader saxReader = new SAXReader();
			saxReader.setEncoding(encode);
			document = saxReader.read(new File(filename));
		} catch (Exception ex) {
			ex.printStackTrace();
		}
		return document;
	}

	/**
	 * 按指定编码转化字符串为Document
	 * 
	 * @param xml
	 * @param encode
	 * @return
	 * @throws UnsupportedEncodingException
	 */
	public static Document loadXml(String xml, String encode) throws UnsupportedEncodingException {
		ByteArrayInputStream inputStream = new ByteArrayInputStream(xml.getBytes(encode));
		return loadXml(inputStream, encode);
	}

	/**
	 * 根据输入流返回Document
	 * 
	 * @param is
	 * @return
	 */
	public static Document loadXml(InputStream is) {
		return loadXml(is, StringPool.UTF_8);
	}

	public static Document loadXml(InputStream is, String charset) {
		Document document = null;
		try {
			SAXReader reader = new SAXReader();
			reader.setEncoding(charset);
			document = reader.read(is);
		} catch (Exception ex) {
			ex.printStackTrace();
		}
		return document;
	}

	/**
	 * 将DOM写入到文件
	 * 
	 * @param document
	 * @param fileName
	 * @throws IOException
	 */
	public static void write(Document document, String fileName) throws IOException {
		String xml = document.asXML();
		FileUtil.writeFile(fileName, xml);
	}

	/**
	 * 将XML写入文件
	 * 
	 * @param str
	 * @param fileName
	 * @throws IOException
	 * @throws DocumentException
	 */
	public static void write(String str, String fileName) throws IOException, DocumentException {
		Document document = DocumentHelper.parseText(str);
		write(document, fileName);
	}

	/**
	 * 根据URL取得DOM
	 * 
	 * @param url
	 * @return
	 * @throws DocumentException
	 */
	public Document load(URL url) throws DocumentException {
		SAXReader reader = new SAXReader();
		Document document = reader.read(url);
		return document;
	}

	/**
	 * 载入一个xml文档
	 * 
	 * @param filename
	 * @return 成功返回Document对象，失败返回null
	 */
	public static Document load(String filename) {
		Document document = null;
		try {
			SAXReader reader = new SAXReader();
			document = reader.read(new File(filename));
			document.normalize();
		} catch (Exception ex) {
			ex.printStackTrace();
		}
		return document;
	}

	/**
	 * 
	 * 
	 * @param filePath
	 * @return
	 * @throws IOException
	 * @throws DocumentException
	 */
	public static Document loadByClassPath(String filePath) throws IOException, DocumentException {
		return Dom4jUtil.loadByClassPath(filePath, StringPool.UTF_8);
	}

	public static Document loadByClassPath(String filePath, String charset) throws IOException, DocumentException {
		return Dom4jUtil.loadByClassPath(filePath, charset, null);
	}

	public static Document loadByClassPath(String filePath, String charset, ClassLoader classLoader)
			throws IOException, DocumentException {

		InputStream is = null;
		Document document = null;
		URL url = null;

		if (classLoader != null) {
			url = classLoader.getResource("/" + filePath);
		} else {
			url = Dom4jUtil.class.getResource("/" + filePath);
		}

		try {
			is = url.openStream();
			InputStreamReader isr = new InputStreamReader(is);
			SAXReader reader = new SAXReader();
			reader.setEncoding(charset);
			document = reader.read(isr);
			document.normalize();
			return document;
		} finally {
			if (is != null) {
				is.close();
			}
		}
	}

	/**
	 * 根据xsl转换xml dom.
	 * 
	 * @param document
	 * @param stylesheet
	 * @return
	 * @throws Exception
	 */
	public static String transFormXsl(String xml, String xsl, Map<String, String> map) throws Exception {

		StringReader xmlReader = new StringReader(xml);
		StringReader xslReader = new StringReader(xsl);
		System.setProperty("javax.xml.transform.TransformerFactory",
				"org.apache.xalan.processor.TransformerFactoryImpl");

		TransformerFactory factory = TransformerFactory.newInstance();
		Transformer transformer = factory.newTransformer(new StreamSource(xslReader));
		if (map != null) {
			// 添加参数
			Iterator<Map.Entry<String, String>> it = map.entrySet().iterator();
			while (it.hasNext()) {
				Map.Entry<String, String> obj = it.next();
				transformer.setParameter(obj.getKey(), obj.getValue());
			}
		}
		StreamSource xmlSource = new StreamSource(xmlReader);

		StringWriter writer = new StringWriter();
		Result result = new StreamResult(writer);
		transformer.transform(xmlSource, result);

		return writer.toString();
	}

	public static String transXmlByXslt(String xml, String xslPath, Map<String, String> map) throws Exception {
		Document document = loadXml(xml);
		document.setXMLEncoding(StringPool.UTF_8);

		Document result = styleDocument(document, xslPath, map);

		return docToString(result);
	}

	public static String transXmlByXslt(String xml, InputStream styleStream, Map<String, String> map) throws Exception {
		Document document = loadXml(xml);
		document.setXMLEncoding(StringPool.UTF_8);

		Document result = styleDocument(document, styleStream, map);

		return docToString(result);
	}

	public static String transFileXmlByXslt(String xmlPath, String xslPath, Map<String, String> map) throws Exception {
		Document document = load(xmlPath);
		document.setXMLEncoding(StringPool.UTF_8);

		Document result = styleDocument(document, xslPath, map);

		return docToString(result);
	}

	/**
	 * 把Document对象转成XML String
	 * 
	 * @param document
	 * @return
	 */
	public static String docToString(Document document) {
		String s = "";
		try {
			ByteArrayOutputStream out = new ByteArrayOutputStream();
			OutputFormat format = new OutputFormat("  ", true, StringPool.UTF_8);
			XMLWriter writer = new XMLWriter(out, format);
			writer.write(document);
			s = out.toString(StringPool.UTF_8);
		} catch (Exception ex) {
			ex.printStackTrace();
		}
		return s;
	}

	/**
	 * document转为xml字符串(带xml缩进格式)
	 * 
	 * @param document
	 * @return
	 */
	public static String docToPrettyString(Document document) {
		String s = "";
		try {
			Writer writer = new StringWriter();
			OutputFormat format = OutputFormat.createPrettyPrint();
			format.setSuppressDeclaration(true); // 去除 <?xml version="1.0"
													// encoding="UTF-8"?>
			XMLWriter xmlWriter = new XMLWriter(writer, format);
			xmlWriter.write(document);
			s = writer.toString();
		} catch (Exception ex) {
			ex.printStackTrace();
		}
		return s;
	}

	/**
	 * 将xml和样式表转成bpmn20xml。
	 * 
	 * @param document
	 * @param stylesheet
	 * @param map
	 * @return
	 * @throws Exception
	 */
	public static Document styleDocument(Document document, String stylesheet, Map<String, String> map)
			throws Exception {
		System.setProperty("javax.xml.transform.TransformerFactory",
				"org.apache.xalan.processor.TransformerFactoryImpl");
		// load the transformer using JAXP
		TransformerFactory factory = TransformerFactory.newInstance();
		Transformer transformer = factory.newTransformer(new StreamSource(stylesheet));
		if (map != null) {
			// 添加参数
			Iterator<Map.Entry<String, String>> it = map.entrySet().iterator();
			while (it.hasNext()) {
				Map.Entry<String, String> obj = it.next();
				transformer.setParameter(obj.getKey(), obj.getValue());
			}
		}
		// now lets style the given document
		DocumentSource source = new DocumentSource(document);
		DocumentResult result = new DocumentResult();
		transformer.transform(source, result);

		// return the transformed document
		Document transformedDoc = result.getDocument();
		return transformedDoc;
	}

	public static Document styleDocument(Document document, InputStream stylesheetStream, Map<String, String> map)
			throws Exception {
		// load the transformer using JAXP

		System.setProperty("javax.xml.transform.TransformerFactory",
				"org.apache.xalan.processor.TransformerFactoryImpl");
		TransformerFactory factory = TransformerFactory.newInstance();
		Transformer transformer = factory.newTransformer(new StreamSource(stylesheetStream));
		if (map != null) {
			// 添加参数
			Iterator<Map.Entry<String, String>> it = map.entrySet().iterator();
			while (it.hasNext()) {
				Map.Entry<String, String> obj = it.next();
				transformer.setParameter(obj.getKey(), obj.getValue());
			}
		}
		// now lets style the given document
		DocumentSource source = new DocumentSource(document);
		DocumentResult result = new DocumentResult();
		transformer.transform(source, result);

		// return the transformed document
		Document transformedDoc = result.getDocument();
		return transformedDoc;
	}

	public static boolean validateXMLSchema(String xml, File... xsdFiles) {
		return validateXMLSchema(new ByteArrayInputStream(xml.getBytes()), xsdFiles);
	}

	public static boolean validateXMLSchema(InputStream xmlIs, File... xsdFiles) {
		try {
			SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);

			List<Source> sourceList = new ArrayList<Source>();

			for (File file : xsdFiles) {
				sourceList.add(new StreamSource(file));
			}

			Source[] sources = sourceList.toArray(new Source[] {});
			Schema schema = factory.newSchema(sources);

			Validator validator = schema.newValidator();
			validator.validate(new StreamSource(xmlIs));
		} catch (IOException e) {
			e.printStackTrace();
			return false;
		} catch (SAXException e) {
			e.printStackTrace();
			return false;
		}
		return true;
	}

	/**
	 * 获取element元素中的属性
	 * 
	 * @param element
	 * @param attrName
	 *            属性名称
	 * @return
	 */
	public static String getString(Element element, String attrName) {
		return getString(element, attrName, false);
	}

	/**
	 * 获取element元素中的属性
	 * 
	 * @param element
	 * @param attrName
	 *            属性名称
	 * @param fuzzy
	 *            是否添加模糊匹配的符号
	 * @return
	 */
	public static String getString(Element element, String attrName, Boolean fuzzy) {
		if (element == null)
			return null;
		String val = element.attributeValue(attrName);
		if (StringUtil.isEmpty(val))
			return null;
		if (fuzzy) {
			val = "%" + val + "%";
		}
		return val;
	}

	/**
	 * 
	 * 读XML文件
	 * 
	 * @param url
	 * @param charsetName
	 * @return
	 */
	public static String readXml(String url, String charsetName) {
		SAXReader reader = new SAXReader();
		reader.setEncoding(charsetName);
		Document document = null;
		try {
			// 获取Document对象
			document = reader.read(url);
		} catch (DocumentException e) {
			e.printStackTrace();
		}
		return document.asXML();
	}

	/**
	 * 
	 * 写XML文件
	 * 
	 * @param xml
	 * @param charsetName
	 * @param path
	 */
	public static void writerXml(String xml, String charsetName, String path) {
		try {
			OutputFormat format = OutputFormat.createPrettyPrint();
			format.setEncoding(charsetName);
			XMLWriter xmlWriter = new XMLWriter(new FileOutputStream(path), format);
			Document doc = DocumentHelper.parseText(xml);
			xmlWriter.write(doc);
			xmlWriter.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	private static List<String> typeList = null;
	public static final String CONTAINS = "contains";
	public static final String START_WITH = "startwith";
	public static final String END_WITH = "endwith";
	static {
		typeList = new ArrayList<String>();
		typeList.add(CONTAINS);
		typeList.add(START_WITH);
		typeList.add(END_WITH);
	}

	/**
	 * 选取文本匹配的子孙节点
	 *
	 * @param root
	 * @param type
	 *            contains/startwith/endwith
	 * @param pattern
	 * @return
	 */
	public static List<Element> selectText(Element root, String type, String pattern) {
		if (StringUtil.isBlank(type)) {
			return null;
		}
		if (!typeList.contains(type)) {
			return null;
		}
		if (StringUtil.isBlank(pattern)) {
			pattern = "";
		}

		if (CONTAINS.equalsIgnoreCase(type)) {
			return selectContainsText(root, pattern);
		} else if (START_WITH.equalsIgnoreCase(type)) {
			return selectStartWithText(root, pattern);
		} else if (END_WITH.equalsIgnoreCase(type)) {
			return selectEndWithText(root, pattern);
		}

		return null;
	}

	/**
	 * 获取所有text以pattern结尾子孙节点
	 *
	 * @param root
	 * @param pattern
	 * @return
	 */
	public static List<Element> selectEndWithText(Element root, String pattern) {
		List<Element> rs = new ArrayList<Element>();

		List<Element> subs = selectText(root);
		for (Element sub : subs) {
			if (sub.getTextTrim().endsWith(pattern)) {
				rs.add(sub);
			}
		}

		return rs;
	}

	/**
	 * 获取所有text以pattern开始子孙节点
	 *
	 * @param root
	 * @param pattern
	 * @return
	 */
	public static List<Element> selectStartWithText(Element root, String pattern) {
		List<Element> rs = new ArrayList<Element>();

		List<Element> subs = selectText(root);
		for (Element sub : subs) {
			if (sub.getTextTrim().startsWith(pattern)) {
				rs.add(sub);
			}
		}

		return rs;
	}

	/**
	 * 获取所有text包含pattern子孙节点
	 *
	 * @param root
	 * @param pattern
	 * @return
	 */
	public static List<Element> selectContainsText(Element root, String pattern) {
		List<Element> rs = new ArrayList<Element>();

		List<Element> subs = selectText(root);
		for (Element sub : subs) {
			if (sub.getTextTrim().contains(pattern)) {
				rs.add(sub);
			}
		}

		return rs;
	}

	@SuppressWarnings("unchecked")
	public static List<Element> selectText(Element root) {
		List<Element> rs = new ArrayList<Element>();
		List<Element> _rs = new ArrayList<Element>();

		List<Element> subs = root.elements();
		if (BeanUtil.isNotEmpty(subs)) {
			for (Element sub : subs) {
				if (StringUtil.isNotBlank(sub.getTextTrim())) {
					rs.add(sub);
				} else {
					_rs = selectText(sub);
					if (BeanUtil.isNotEmpty(_rs)) {
						rs.addAll(_rs);
					}
				}
			}
		}

		return rs;
	}

	public static List<Element> selectNode(Element root, String nodename) {
		List<Element> rs = new ArrayList<Element>();

		List<Element> subs = selectNode(root);
		for (Element sub : subs) {
			if (sub.getName().equals(nodename)) {
				rs.add(sub);
			}
		}

		return rs;
	}

	@SuppressWarnings("unchecked")
	public static List<Element> selectNode(Element root) {
		List<Element> rs = new ArrayList<Element>();
		List<Element> _rs = new ArrayList<Element>();

		List<Element> subs = root.elements();
		if (BeanUtil.isNotEmpty(subs)) {
			for (Element sub : subs) {
				rs.add(sub);
				_rs = selectNode(sub);
				if (BeanUtil.isNotEmpty(_rs)) {
					rs.addAll(_rs);
				}
			}
		}

		return rs;
	}
}
